﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MathService.Values;

namespace MathService.Statistic
{
    /// <summary>Гистограмма</summary>
    public sealed class Histogram : IEnumerable<ValuedInterval<double>>
    {
        public readonly double[] Argument;
        private readonly Interval[] f_Intervals;
        private readonly Interval f_Interval;
        private readonly double f_Normalizator;
        public double Max { get { return f_Interval.Max; } }
        public double Min { get { return f_Interval.Min; } }
        public readonly int N;
        public readonly double[] Values;
        public readonly double[] Percent;

        public ValuedInterval<double> this[int i]
        {
            get { return new ValuedInterval<double>(f_Intervals[i], Values[i]); }
        }

        public Histogram(IEnumerable<double> X, int IntervalsCount)
        {
            var lv_MinMax = new MinMaxValue();
            X = X.ForeachLeazy(lv_MinMax.AddValue);


            var X_Values = X.ToArray();
            N = X_Values.Length;

            f_Interval = lv_MinMax.Interval;

            var dx = (Max - Min) / IntervalsCount;

            f_Intervals = new Interval[IntervalsCount]
                .Initialize(i => new Interval(Min + (i * dx), true, Min + ((i + 1) * dx), false));
            f_Intervals[IntervalsCount - 1].MaxInclude = true;

            Argument = f_Intervals.Select(I => I.Middle).ToArray();
            Values = new double[IntervalsCount];
            Action<double> action = x => f_Intervals.Foreach((interval, i) => { if(interval.Check(x)) Values[i]++; });
            X_Values.Foreach(action);
            Percent = new double[Values.Length].Initialize(i => Values[i] / N);
            Values.Divade(f_Normalizator = Values.GetIntegral(Argument));
        }

        public bool CheckDestribution(Func<double, double> F, double p, double alpha = 0.05)
        {
            var lv_P_theor = new double[Values.Length].Initialize(i =>
            {
                var interval = f_Intervals[i];
                return F.GetIntegralValue(interval.Min,
                                        interval.Max,
                                        interval.Length / 20);
            });

            var stat = Values.GetMultiplyed((f_Normalizator / (N))).Select((t, i) => (lv_P_theor[i] - t)).Select(
                (delta, i) => ((delta * delta) / lv_P_theor[i])).Sum();
            var lv_Quantile = SpecialFunctions.Distribution.Student.QuantileHi2(2, alpha);
            Console.WriteLine(stat < lv_Quantile);
            Console.ReadLine();
            return stat < lv_Quantile;
        }

        public KeyValuePair<Interval, double>[] GetValues()
        {
            return new KeyValuePair<Interval, double>[Values.Length].Initialize(
                i => new KeyValuePair<Interval, double>(f_Intervals[i], Values[i]));
        }

        public KeyValuePair<Interval, double>[] GetPercents()
        {
            return new KeyValuePair<Interval, double>[Percent.Length].Initialize(
                i => new KeyValuePair<Interval, double>(f_Intervals[i], Percent[i]));
        }

        private IEnumerable<ValuedInterval<double>> GetIEnumerable()
        {
            for(var i = 0; i < f_Intervals.Length; i++)
                yield return this[i];
        }

        public IEnumerator<ValuedInterval<double>> GetEnumerator() { return GetIEnumerable().GetEnumerator(); }

        public override string ToString()
        {
            return GetValues()
                    .Aggregate(
                        new StringBuilder(),
                        (sb, v) => sb.AppendFormat("{0}:{1} ", v.Key, v.Value),
                        sb => sb.ToString().RemoveFromEnd(1));
        }

        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

        public string ToString(string Format)
        {
            return GetValues()
                    .Aggregate(
                        new StringBuilder(),
                        (sb, v) => sb.AppendFormat("{0}:{1} ", v.Key, v.Value.ToString(Format)),
                        sb => sb.ToString().RemoveFromEnd(1));
        }

        public string ToString(string IntervalFormat, string ValueFormat)
        {
            return GetValues()
                    .Aggregate(
                        new StringBuilder(),
                        (sb, v) => sb.AppendFormat("{0}:{1} ", v.Key.ToString(IntervalFormat), v.Value.ToString(ValueFormat)),
                        sb => sb.ToString().RemoveFromEnd(1));
        }
    }
}

